﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SchEdu.Framework.Algorithm;
using SchEdu.Framework.DataModel;
using SchEdu.Framework.Validators;
using SchEdu.Framework.Exceptions;

namespace SBH
{
    public class SBHAlgorithm : Algorithm
    {
        public SBHAlgorithm()
        {
        }

        public SBHAlgorithm(SchEduData schEduData) 
            :base(schEduData)
        {
        }

        protected override void InitializeValidators()
        {
            base.InitializeValidators();

            validators.Add(new OperationWorkCenterValidator(SchEduData));
            validators.Add(new MachinePerWorkCenterValidator(SchEduData,1,1));
            validators.Add(new OperationPerOrderValidator(SchEduData, 1, -1));
            validators.Add(new OperationSequenceNoValidator(SchEduData));

        }

        int Cmax = Int32.MinValue;
        WorkCenterOperationList workCenterOperationSequence = new WorkCenterOperationList();

        protected override void Logic()
        {

            Cmax = Int32.MinValue;
            workCenterOperationSequence = new WorkCenterOperationList();

            // Calculate longest path
            Cmax = OrderOperationSortedList.GetCmax();

            while (WorkCenterOperationList.Count > 0)
            {
                
                SetReleaseDates();
                SetDueDates();

                // find bottleneck
                KeyValuePair<WorkCenterKey, OperationList> workCenterBottleneck = new KeyValuePair<WorkCenterKey, OperationList>();
                int bottleneckLmax = Int32.MinValue;
                foreach (KeyValuePair<WorkCenterKey, OperationList> a in WorkCenterOperationList)
                {
                    OperationList minLmaxOperationList = MinimizeLmax(a.Value);
                    int Lmax = CalculateLateness(minLmaxOperationList);
                    if (Lmax > bottleneckLmax)
                    {
                        workCenterBottleneck = new KeyValuePair<WorkCenterKey, OperationList>(a.Key, minLmaxOperationList);
                        bottleneckLmax = Lmax;
                    }
                }
                CalculateLateness(workCenterBottleneck.Value);
                workCenterOperationSequence.Add(workCenterBottleneck.Key, workCenterBottleneck.Value);
                WorkCenterOperationList.Remove(workCenterBottleneck.Key);
                Cmax += bottleneckLmax;
            }

            CreateAllocation();
        }

        protected OperationList MinimizeLmax(OperationList operations)
        {
            OperationList op = new OperationList();
            IEnumerable<IEnumerable<SchEduDataSet.OperationRow>> permutations = Permuter.Permute(operations);
            int minLmax = Int32.MaxValue;
            foreach (IEnumerable<SchEduDataSet.OperationRow> p in permutations)
            {
                int lateness = CalculateLateness(p);
                if (lateness < minLmax)
                {
                    op =  new OperationList(p);
                    minLmax = lateness;
                }
            }
            return op;
        }

        protected int CalculateLateness(IEnumerable<SchEduDataSet.OperationRow> operations)
        {
            int availabilityDate = 0;
            int Lmax = Int32.MinValue;
            foreach(SchEduDataSet.OperationRow op in operations)
            {
                if (availabilityDate < op.AvailabilityDate)
                {
                    availabilityDate = op.AvailabilityDate;
                }
                op.StartDate = availabilityDate;
                op.EndDate = availabilityDate + op.ProcessingTime - 1;
                int lateness = Math.Max(0, op.EndDate - op.DueDate);

                Lmax = Math.Max(Lmax, lateness);
                availabilityDate = op.EndDate + 1;
            }

            return Lmax;
        }

        protected void SetReleaseDates()
        {
            foreach (KeyValuePair<SchEduDataSet.OrderRow, OperationSortedList> a in OrderOperationSortedList)
            {
                int availabilityDate = 0;
                foreach (SchEduDataSet.OperationRow op in a.Value.Values)
                {
                    if (workCenterOperationSequence.ContainsWorkCenter(op.WorkCenterRow) == true)
                    {
                        availabilityDate = op.EndDate + 1;
                        continue;
                    }
                    op.AvailabilityDate = availabilityDate;
                    availabilityDate += op.ProcessingTime;
                }
            }
        }

        protected void SetDueDates()
        {
            foreach (KeyValuePair<SchEduDataSet.OrderRow, OperationSortedList> a in OrderOperationSortedList)
            {
                int dueDate = 0;
                for (int i = a.Value.Count - 1; i >= 0; i--)
                {
                    if (workCenterOperationSequence.ContainsWorkCenter(a.Value.Values[i].WorkCenterRow) == true)
                    {
                        continue;
                    }

                    a.Value.Values[i].DueDate = Cmax - dueDate;
                    dueDate += a.Value.Values[i].ProcessingTime;
                }
            }
        }

        protected void CreateAllocation()
        {
            foreach(SchEduDataSet.WorkCenterRow workCenter in WorkCenters)
            {
                workCenter.AvailabilityDate = 0;
            }
            foreach(SchEduDataSet.OrderRow order in Orders)
            {
                order.AvailabilityDate = 0;
            }

            OperationList nextOperationByWorkCenter = workCenterOperationSequence.GetNextOperations();
            while (nextOperationByWorkCenter.Count > 0)
            {
                OperationList nextOperationByOrder = OrderOperationSortedList.GetNextOperations();
                SchEduDataSet.OperationRow nextOperation = null;
                foreach (SchEduDataSet.OperationRow op in nextOperationByWorkCenter)
                {
                    if (nextOperationByOrder.Contains(op) == true)
                    {
                        nextOperation = op;
                        break;
                    }
                }
                if (nextOperation == null)
                {
                    throw new SchEduException(Resource.NoSolutionMessage);
                }
                nextOperation.StartDate = Math.Max(nextOperation.WorkCenterRow.AvailabilityDate, nextOperation.OrderRow.AvailabilityDate);
                nextOperation.EndDate = nextOperation.StartDate + nextOperation.ProcessingTime - 1;
                nextOperation.WorkCenterRow.AvailabilityDate = nextOperation.EndDate + 1;
                nextOperation.OrderRow.AvailabilityDate = nextOperation.EndDate + 1;

                workCenterOperationSequence.RemoveByOperation(nextOperation);
                OrderOperationSortedList.RemoveByOperation(nextOperation);
                nextOperationByWorkCenter = workCenterOperationSequence.GetNextOperations();
            }

             // Assign Machine
            foreach (SchEduDataSet.OperationRow operation in SchEduData.Operation)
            {
                operation.MachineID = operation.WorkCenterRow.GetMachineRows()[0].ID;
            }
            
        }


        public override AboutInfo AboutInfo
        {
            get
            {
                AboutInfo aboutInfo = new AboutInfo();
                aboutInfo.Title = "Shifting Bottleneck Heuristic";
                aboutInfo.Description = Resource.SBHAlgorithmDescription;
                aboutInfo.Guid = new Guid("{8B37D44C-5716-4867-9A9B-5F6C4CF9CB46}");
                return aboutInfo;
            }
        }

    }
}
